#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <ctype.h>

#include "NativeMath.h"

void assignint(const int value, const ninteger* target) 
{
	target->content->values[0] = (UINT)value;

	if(target->issigned) 
	{
		if(target->width < BASE_TYPE_BITS) 
		{			
			signextend(target, target->width - 1);
		}
		else 
		{
			signextend(target, BASE_TYPE_BITS - 1);
		}
	}
	else  // not signed
	{
		if(target->width % BASE_TYPE_BITS != 0) 
		{
			target->content->values[target->content->length - 1] &= bitmasks[target->width % BASE_TYPE_BITS];
		}
	}
}

void assignuint(const UINT value, const ninteger* target) 
{
	assignint((const int)value, target);
}

void assignlong(const LONG  value, const ninteger* target)
{
	/* code assumes 8 byte longs */
	assert(sizeof(LONG) == 8);
	target->content->values[0] = (UINT)value;

	if (target->content->length > 1)
		target->content->values[1] = (UINT)(value >> BASE_TYPE_BITS);

	if (target->issigned)
	{
		if (target->width < 64)
			signextend(target, target->width - 1);
		else
			signextend(target, 63);
	}
	else
	{
		if ((target->width % BASE_TYPE_BITS) != 0)
			target->content->values[target->content->length - 1] &= bitmasks[target->width % BASE_TYPE_BITS];
	}
}

BOOL isoctal(const char c) 
{
	return isdigit(c) && c != 8 && c != 9;
}

int ctoi(const char c) 
{
	char uc = toupper(c);
	
	assert(isxdigit(c));
	switch(uc)
	{
	case '0':
		return 0;
	case '1':
		return 1;
	case '2':
		return 2;
	case '3':
		return 3;
	case '4':
		return 4;
	case '5':
		return 5;
	case '6':
		return 6;
	case '7':
		return 7;
	case '8':
		return 8;
	case '9':
		return 9;
	case 'A':
		return 10;
	case 'B':
		return 11;
	case 'C':
		return 12;
	case 'D':
		return 13;
	case 'E':
		return 14;
	case 'F':
		return 15;
	default:
		perror("invalid hex digit");
		return -1;
	}
}

strtype getstrtype(const char* str) 
{
	UINT i;
	size_t length = strlen(str);

	if(length == 0) 
	{
		return strtype_invalid;
	}
	else if(length == 1) 
	{
		return strtype_dec;
	}
	else 
	{
		if(str[0] == '0') 
		{
			if(str[1] == 'b' || str[1] == 'B') 
			{
				for(i = 2; i < length; i++)
				{
					if(str[i] != '0' && str[i] != '1')
						return strtype_invalid;
				}
				return strtype_bin;
			} 
			else if(str[1] == 'x' || str[1] == 'X') 
			{
				for(i = 2; i < length; i++) 
				{
					if(!isxdigit(str[i]))
						return strtype_invalid;
				}
				return strtype_hex;
			}
			else 
			{
				for(i = 2; i < length; i++) 
				{
					if(!isoctal(str[i])) 
						return strtype_invalid;
				}
				return strtype_oct;
			}
		}
		else 
		{
			if(str[0] == '-' || isdigit(str[0])) 
			{
				for(i = 1; i < length; i++) 
				{
					if(!isdigit(str[i]))
						return strtype_invalid;
				}
				return strtype_dec;
			}
		}
	}

	return strtype_invalid;
}


void assignbinstr(char* value, const ninteger* target) 
{
	const size_t length = strlen(value);
	UINT j = 0;

	/* clear contents */
	memset(target->content->values, 0, target->content->length * sizeof(UINT));

	/* todo: trim string */
	/* value = trim(value); */

	if(length - 2 > target->width)
		perror("width mismatch");

	for (; j < length; j++)
		if (value[length - j - 1] == '1')
			setbit(target, j);

	if (target->issigned)
		signextend(target, target->width - 1);
}

/*
 * Assign the present integer the value of the single string argument
 * interpreted as a decimal number.
 */
void assigndecstr(char* value, const ninteger* target) 
{
	UINT i;
	int index = 0;
	const size_t slen = strlen(value);
	BOOL negateresult = FALSE;

	assert(getstrtype(value) == strtype_dec);

	
	if (value[index] == '-')
	{
		negateresult = TRUE;
		index++;
	}

	assignint(ctoi(value[index++]), target);

	for (i = index; i < slen; i++)
	{
		mulinternal2(target->content, 10u, target->content);
		addinternal2(target->content, ctoi(value[i]), target->content);
	}

	if (negateresult)
		negateinternal(target->content, target->content);
}

/*
 * Assign the present integer the value of the single string argument
 * interpreted as a hexadecimal number.
 */
void assignhexstr(char* value, const ninteger* target) 
{
	size_t i;
	const size_t length = strlen(value);

	/* clear contents */
	memset(target->content->values, 0, sizeof(UINT));

	/* todo: trim string */
	/* value = trim(value); */

	/* compare width */
	if ((length - 2u) > ((target->width / 4u) + 1u))
		perror("width mismatch");

	for(i = 2; i < length; i++)
	{
		mulinternal2(target->content, 0x10, target->content);
		addinternal2(target->content, ctoi(value[i]), target->content);
	}

	// Only sign extend if the integer is signed ..
	if (target->issigned)
	{
		signextend(target, target->width - 1);
	}
	else
	{
		if (target->width % BASE_TYPE_BITS != 0)
			target->content->values[target->content->length - 1] &= bitmasks[target->width % BASE_TYPE_BITS];
	}
}

void assignstr(char* value, const ninteger* target) 
{
	switch(getstrtype(value)) 
	{
	case strtype_bin:
		assignbinstr(value, target);
		break;
	case strtype_oct:
		perror("not implemented");
		break;
	case strtype_dec:
		assigndecstr(value, target);
		break;
	case strtype_hex:
		assignhexstr(value, target);
		break;
	case strtype_invalid:
	default:  
		perror("invalid string for assignment");
	}
}